#include "inquiry.h"

// macro pour detruire une image Iplimage si elle existe
#define SAFE_IMG_RELEASE(img) \
if(img) \
{\
    cvReleaseImage(&img);\
    img = NULL;\
}

Inquiry::Inquiry(QString nameTest) : nameDir(nameTest), cvimage0(NULL), cvimage1(NULL), psnrMin(0), ssimMin(0), usePsnr(false), useSsim(false), inRefresh(false){}

Inquiry::~Inquiry()
{
    SAFE_IMG_RELEASE(cvimage0);
    SAFE_IMG_RELEASE(cvimage1);
}

void Inquiry::setNameTest(QString const nameTest)
// Definit le nom du dosssier dans lequel sera enregistre les images
{
    nameDir = nameTest;
}

void Inquiry::run()
// Lancement du processus
{
    if (nameDir.isEmpty()){
        nameDir = QDateTime::currentDateTime().toString();
    }
    QDir lDir;
    lDir.mkdir(nameDir);
    lDir.mkdir(nameDir+"/images");

    // On copie une premiere image pour avoir cvimage0 utilisable dans refresh
    bool cvimageNOK=true;
    while(cvimageNOK){
        locker.lock();
        if(cvimageinter){
            cvimage1 = cvCloneImage(cvimageinter);
            cvimageNOK=false;
        }
        locker.unlock();
        if(cvimageNOK){
            sleep(10);
        }
    }

    usePsnr=true;
    psnrMin=15;

    // Creation et lancement du QTimer pour executer refresh en continu.
    timer = new QTimer();
    timer->moveToThread(QThread::currentThread());
    connect(timer, SIGNAL(timeout()), this, SLOT(refresh()), Qt::DirectConnection);
    connect(this, SIGNAL(stopSignal()), timer, SLOT(stop()), Qt::DirectConnection);
    timer->start(40);
    exec();
}

void Inquiry::terminate()
// Surcharge de terminate pour arreter le thread proprement
{
    emit stopSignal();
}

void Inquiry::refresh()
// 25 fois par seconde, .
{
    if(inRefresh){
        return;
    }
    inRefresh=true;

    SAFE_IMG_RELEASE(cvimage0);
    cvimage0 = cvimage1;

    locker.lock();
    if(cvimageinter){
        cvimage1 = cvCloneImage(cvimageinter);
    }
    else {
        qDebug() << "erreur de copie de cvimageinter dans Inquiry::refresh();";
    }
    locker.unlock();

    if(changedImage()){
        saveImage();
        qDebug() << "Nouvelle image détectées.";
    }
    inRefresh=false;
}

bool Inquiry::changedImage() const
// Fonction pour test si cvimage0 est different de cvimage1 selon un certain seuil et en fonction de l'algorithme choisi.
{
    if(usePsnr){
        double truc = getPSNR();
        if(truc<psnrMin){
            return true;
        }
    }
    else if(useSsim){
        CvScalar ssim = getSSIM();
        if(ssim.val[2]+ssim.val[1]+ssim.val[0]<ssimMin*3){
            return true;
        }
    }
    return false;
}

bool Inquiry::saveImage() const
{
    QString imageName(nameDir);
    imageName += "/images/";
    imageName += QDateTime::currentDateTime().toString("MM.dd hh:mm:ss.zzz");
    imageName += ".jpg";

    qDebug() << imageName;
    cvSaveImage(imageName.toStdString().c_str(), cvimage1);
}

double Inquiry::getPSNR() const
// Renvoie le PSNR entre cvimage0 et cvimage1.
{
    Mat I1 = Mat(cvimage0);
    Mat I2 = Mat(cvimage1);
    Mat s1;
    absdiff(I1, I2, s1);       // |I1 - I2|
    s1.convertTo(s1, CV_32F);  // cannot make a square on 8 bits
    s1 = s1.mul(s1);           // |I1 - I2|^2

    Scalar s = sum(s1);         // sum elements per channel

    double sse = s.val[0] + s.val[1] + s.val[2]; // sum channels

    if( sse <= 1e-10) // for small values return zero
    {
        return 666;
    }
    else
    {
        double mse =sse /(double)(I1.channels() * I1.total());
        double psnr = 10.0*log10((255*255)/mse);
        return psnr;
    }
    return 18;
}

CvScalar Inquiry::getSSIM() const
//renvoie le simm entre cvimage0 et cvimage1
{
    if(cvimage0==NULL||cvimage1==NULL)
    {
        CvScalar truc = {0, 0, 0, 0};
        return truc;
    }

    // default settings
    double C1 = 6.5025, C2 = 58.5225;

    IplImage
        *img1=NULL, *img2=NULL, *img1_img2=NULL,
        *img1_temp=NULL, *img2_temp=NULL,
        *img1_sq=NULL, *img2_sq=NULL,
        *mu1=NULL, *mu2=NULL,
        *mu1_sq=NULL, *mu2_sq=NULL, *mu1_mu2=NULL,
        *sigma1_sq=NULL, *sigma2_sq=NULL, *sigma12=NULL,
        *ssim_map=NULL, *temp1=NULL, *temp2=NULL, *temp3=NULL;

    /***************************** INITS **********************************/
    img1_temp = cvCloneImage(cvimage0);
    img2_temp = cvCloneImage(cvimage1);

    int x=img1_temp->width, y=img1_temp->height;
    int nChan=img1_temp->nChannels, d=IPL_DEPTH_32F;
    CvSize size = cvSize(x, y);

    img1 = cvCreateImage( size, d, nChan);
    img2 = cvCreateImage( size, d, nChan);

    cvConvert(img1_temp, img1);
    cvConvert(img2_temp, img2);
    cvReleaseImage(&img1_temp);
    cvReleaseImage(&img2_temp);


    img1_sq = cvCreateImage( size, d, nChan);
    img2_sq = cvCreateImage( size, d, nChan);
    img1_img2 = cvCreateImage( size, d, nChan);

    cvPow( img1, img1_sq, 2 );
    cvPow( img2, img2_sq, 2 );
    cvMul( img1, img2, img1_img2, 1 );

    mu1 = cvCreateImage( size, d, nChan);
    mu2 = cvCreateImage( size, d, nChan);

    mu1_sq = cvCreateImage( size, d, nChan);
    mu2_sq = cvCreateImage( size, d, nChan);
    mu1_mu2 = cvCreateImage( size, d, nChan);


    sigma1_sq = cvCreateImage( size, d, nChan);
    sigma2_sq = cvCreateImage( size, d, nChan);
    sigma12 = cvCreateImage( size, d, nChan);

    temp1 = cvCreateImage( size, d, nChan);
    temp2 = cvCreateImage( size, d, nChan);
    temp3 = cvCreateImage( size, d, nChan);

    ssim_map = cvCreateImage( size, d, nChan);
    /*************************** END INITS **********************************/


    //////////////////////////////////////////////////////////////////////////
    // PRELIMINARY COMPUTING
    cvSmooth( img1, mu1, CV_GAUSSIAN, 11, 11, 1.5 );
    cvSmooth( img2, mu2, CV_GAUSSIAN, 11, 11, 1.5 );

    cvPow( mu1, mu1_sq, 2 );
    cvPow( mu2, mu2_sq, 2 );
    cvMul( mu1, mu2, mu1_mu2, 1 );


    cvSmooth( img1_sq, sigma1_sq, CV_GAUSSIAN, 11, 11, 1.5 );
    cvAddWeighted( sigma1_sq, 1, mu1_sq, -1, 0, sigma1_sq );

    cvSmooth( img2_sq, sigma2_sq, CV_GAUSSIAN, 11, 11, 1.5 );
    cvAddWeighted( sigma2_sq, 1, mu2_sq, -1, 0, sigma2_sq );

    cvSmooth( img1_img2, sigma12, CV_GAUSSIAN, 11, 11, 1.5 );
    cvAddWeighted( sigma12, 1, mu1_mu2, -1, 0, sigma12 );


    //////////////////////////////////////////////////////////////////////////
    // FORMULA

    // (2*mu1_mu2 + C1)
    cvScale( mu1_mu2, temp1, 2 );
    cvAddS( temp1, cvScalarAll(C1), temp1 );

    // (2*sigma12 + C2)
    cvScale( sigma12, temp2, 2 );
    cvAddS( temp2, cvScalarAll(C2), temp2 );

    // ((2*mu1_mu2 + C1).*(2*sigma12 + C2))
    cvMul( temp1, temp2, temp3, 1 );

    // (mu1_sq + mu2_sq + C1)
    cvAdd( mu1_sq, mu2_sq, temp1 );
    cvAddS( temp1, cvScalarAll(C1), temp1 );

    // (sigma1_sq + sigma2_sq + C2)
    cvAdd( sigma1_sq, sigma2_sq, temp2 );
    cvAddS( temp2, cvScalarAll(C2), temp2 );

    // ((mu1_sq + mu2_sq + C1).*(sigma1_sq + sigma2_sq + C2))
    cvMul( temp1, temp2, temp1, 1 );

    // ((2*mu1_mu2 + C1).*(2*sigma12 + C2))./((mu1_sq + mu2_sq + C1).*(sigma1_sq + sigma2_sq + C2))
    cvDiv( temp3, temp1, ssim_map, 1 );


    CvScalar index_scalar = cvAvg( ssim_map );

    // through observation, there is approximately
    // 1% error max with the original matlab program

//    cout << "(R, G & B SSIM index)" << endl ;
//    cout << index_scalar.val[2] * 100 << "%" << endl ;
//    cout << index_scalar.val[1] * 100 << "%" << endl ;
//    cout << index_scalar.val[0] * 100 << "%" << endl ;

    // if you use this code within a program
    // don't forget to release the IplImages


    SAFE_IMG_RELEASE(img1);
    SAFE_IMG_RELEASE(img2);  SAFE_IMG_RELEASE(img1_img2);
    SAFE_IMG_RELEASE(img1_temp);  SAFE_IMG_RELEASE(img2_temp);
    SAFE_IMG_RELEASE(img1_sq);  SAFE_IMG_RELEASE(img2_sq);
    SAFE_IMG_RELEASE(mu1);  SAFE_IMG_RELEASE(mu2);
    SAFE_IMG_RELEASE(mu1_sq);  SAFE_IMG_RELEASE(mu2_sq);  SAFE_IMG_RELEASE(mu1_mu2);
    SAFE_IMG_RELEASE(sigma1_sq);  SAFE_IMG_RELEASE(sigma2_sq);  SAFE_IMG_RELEASE(sigma12);
    SAFE_IMG_RELEASE(ssim_map);  SAFE_IMG_RELEASE(temp1);  SAFE_IMG_RELEASE(temp2);  SAFE_IMG_RELEASE(temp3);


    return index_scalar;
}
